package com.flashflexpro.flexdirector.adapter{
    import com.flashflexpro.flexdirector.adapter.descriptor.DescEvent;
    import com.flashflexpro.flexdirector.adapter.descriptor.DescMethod;
    import com.flashflexpro.flexdirector.adapter.descriptor.DescPropertyAuto;

    import flash.display.DisplayObject;
    import flash.system.ApplicationDomain;
    import flash.utils.getQualifiedClassName;

    import mx.automation.AutomationClass;
    import mx.automation.AutomationManager;
    import mx.automation.IAutomationClass;
    import mx.automation.IAutomationEnvironment;
    import mx.automation.IAutomationEventDescriptor;
    import mx.automation.IAutomationMethodDescriptor;
    import mx.automation.IAutomationObject;

    public class AutoEnv implements IAutomationEnvironment{

        private var automationClassName2automationClass:Object = {};
        private var className2automationClass:Object = {};

        public static function get inst():AutoEnv{
            return _inst;
        }

        private static var _inst:AutoEnv;

        public function AutoEnv( src:XML ){
            _inst = this;
            var source:XML = EnvXMLParser.parse( src );


            for( var i:Object in source.ClassInfo ){
                // populate the class/automationClass map
                var classInfoXML:XML = source.ClassInfo[i];
                var automationClassName:String = classInfoXML.@Name.toString();
                var superClassName:String = classInfoXML.@Extends && classInfoXML.@Extends.length != 0 ?
                        classInfoXML.@Extends.toString() : null;
                var automationClass:AutomationClass = new AutomationClass( automationClassName, superClassName );
                automationClassName2automationClass[automationClassName] = automationClass;

                for( var j:Object in classInfoXML.Implementation ){
                    var implementationXML:XML = classInfoXML.Implementation[j];
                    var realClassName:String = implementationXML.@Class.toString().replace( "::", "." );
                    className2automationClass[realClassName] = automationClass;
                    automationClass.addImplementationClassName( realClassName );
                }

                var pd:DescPropertyAuto;

                // for each Method
                for( var k:Object in classInfoXML.Methods.Method ){
                    var methodXML:XML = classInfoXML.Methods.Method[k];

                    var automationMethodName:String = methodXML.@Name.toString();
                    var methodName:String = methodXML.Implementation.@Type.toString();

                    if( methodName ){
                        var parameters:Array = [];
                        for( var m:Object in methodXML.Parameter ){
                            var parameterXML:XML = methodXML.Parameter[m];
                            var parameterName:String = parameterXML.@Name.toString();
                            var parameterType:String = parameterXML.PropertyType.@Type.toString().toLowerCase();
                            var parameterCodec:String = parameterXML.PropertyType.@Codec.toString();
                            pd = new DescPropertyAuto();
                            pd.name = parameterName;
                            pd.forDescription = true;
                            pd.forVerification = true;

                            pd.asType = parameterType;
                            pd.codecName = parameterCodec;
                            pd.defaultValue = null;
                            parameters.push( pd );
                        }

                        var returnType:String = methodXML.ReturnValueType.PropertyType.@Type.toString();

                        var codecName:String = methodXML.ReturnValueType.Type.@Codec.toString();

                        var md:IAutomationMethodDescriptor = new DescMethod( automationMethodName, methodName,
                                returnType, codecName, parameters );
                        automationClass.addMethod( md );

                    }
                }


                // for each Event
                for( var q:Object in classInfoXML.Events.Event ){
                    var eventXML:XML = classInfoXML.Events.Event[q];

                    var automationEventName:String = eventXML.@Name.toString();
                    var eventClassName:String = eventXML.Implementation.@Class.toString();
                    eventClassName = eventClassName.replace( "::", "." );
                    var eventType:String = eventXML.Implementation.@Type.toString();

                    if( eventType ){
                        var properties:Array = [];
                        for( var n:Object in eventXML.Property ){
                            var eventPropertyXML:XML = eventXML.Property[n];
                            var eventPropertyName:String = eventPropertyXML.@Name.toString();
                            var eventPropertyType:String = eventPropertyXML.PropertyType.@Type.toString().toLowerCase();
                            var eventPropertyCodec:String = eventPropertyXML.PropertyType.@Codec.toString();
                            var defaultValue:String = eventPropertyXML.@DefaultValue.length() > 0 ?
                                    eventPropertyXML.@DefaultValue.toString() : null;
                            pd = new DescPropertyAuto();
                            pd.name = eventPropertyName;
                            pd.forDescription = true;
                            pd.forVerification = true;
                            pd.asType = eventPropertyType;
                            pd.codecName = (eventPropertyCodec == null || eventPropertyCodec.length == 0 ? "object" :
                                    eventPropertyCodec);
                            pd.defaultValue = defaultValue;
                            properties.push( pd );
                        }

                        var ed:IAutomationEventDescriptor = new DescEvent( automationEventName, eventClassName,
                                eventType, properties );
                        automationClass.addEvent( ed );
                    }
                }

                // for each Property
                for( var p:Object in classInfoXML.Properties.Property ){
                    var propertyXML:XML = classInfoXML.Properties.Property[p];
                    var propName:String = propertyXML.@Name.toString();
                    var propType:String = propertyXML.PropertyType.@Type.toString().toLowerCase();
                    var propCodec:String = propertyXML.PropertyType.@Codec.toString();
                    pd = new DescPropertyAuto();

                    pd.name = propName;
                    pd.forDescription = propertyXML.@ForDescription.toString() == "true";
                    pd.forVerification = propertyXML.@ForVerification.toString() == "true";
                    pd.asType = propType;
                    pd.codecName = propCodec == null || propCodec.length == 0 ? "object" : propCodec;
                    pd.defaultValue = null;
                    automationClass.addPropertyDescriptor( pd );
                }

            }
            

        }


        /**
         *  @private
         */
        public function getAutomationClassByInstance( obj:IAutomationObject ):IAutomationClass{
            var result:IAutomationClass = findClosestAncestor( obj );
            if( ! result ){
                throw new Error( "Automtaion Class Not Found : " + getClassName( obj ) );
            }
            return result;
        }

        /**
         *  @private
         */
        public function getAutomationClassByName( automationClass:String ):IAutomationClass{
            var rt:IAutomationClass = IAutomationClass( automationClassName2automationClass[automationClass] );
            return rt;
        }

        /**
         *  Finds the closest ancestor to this object about which information was
         *  passed in the environment XML.
         *  @private
         */
        private function findClosestAncestor( obj:Object ):IAutomationClass{
            var className:String = getClassName( obj );
            if( className in className2automationClass ){
                return className2automationClass[className];
            }

            var ancestors:Array = findAllAncestors( obj );
            if( ancestors.length != 0 ){
                className2automationClass[className] = ancestors[0];
                return className2automationClass[className];
            }
            else{
                return null;
            }
        }

        /**
         *  @private
         */
        private function findAllAncestors( obj:Object ):Array{
            var result:Array = [];

            for( var i:String in className2automationClass ){
                var realClass:Class = getDefinitionFromObjectDomain( obj, i );
                if( realClass && obj is realClass ){
                    result.push( className2automationClass[i] );
                }
            }

            result.sort( sortAncestors );

            return result;
        }

        /**
         *  @private
         */
        private function sortAncestors( a:IAutomationClass, b:IAutomationClass ):int{
            var superClass:IAutomationClass;
            var x:String = a.superClassName;
            while( x ){
                if( x == b.name ){
                    return -1;
                }
                superClass = getAutomationClassByName( x );
                x = superClass.superClassName;
            }

            x = b.superClassName;
            while( x ){
                if( x == a.name ){
                    return 1;
                }
                superClass = getAutomationClassByName( x );
                x = superClass.superClassName;
            }

            return 0;
        }

        public static function getClassName( obj:Object ):String{
            return getQualifiedClassName( obj ).replace( "::", "." );
        }

        /**
         *  Utility function that returns the class definition from the domain of a
         *  object instance
         */
        public static function getDefinitionFromObjectDomain( obj:Object, className:String ):Class{
            var eventClass:Class;
            try{
                eventClass = Class( ApplicationDomain.currentDomain.getDefinition( className ) );
            }
            catch( e:Error ){
                // we can get a reference or security exception
                // in which case we try to access the objects own domain
                try{
                    var dispObj:DisplayObject = obj as DisplayObject;
                    eventClass = Class( dispObj.root.loaderInfo.applicationDomain.getDefinition( className ) );
                }
                catch( e:Error ){
                    // we can get a reference or security exception

                    // In this case we assume that the class definition is not available
                    // as the class has not been referenced in the app and it has
                    // not been linked in.
                }
            }

            return eventClass;
        }

        private var atmng:AutomationManager;
    }
}